/**
 *   _                                                          _   
 *  | | ____ _ _ __ _ __ ___   __ _  ___           _ __ ___ ___| |_ 
 *  | |/ / _` | '__| '_ ` _ \ / _` |/ __|  _____  | '__/ _ | __| __|
 *  |   < (_| | |  | | | | | | (_| | (__  |_____| | | |  __|__ \ |_ 
 *  |_|\_\__,_|_|  |_| |_| |_|\__,_|\___|         |_|  \___|___/\__|
 *         
 *  Copyright  2011 Kondra, Inc. 
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.kondra.karmac.rest
{
   import flash.net.URLRequest;
   import flash.net.URLRequestHeader;
   import flash.net.URLVariables;

   /**
    * The <code>ServiceEndpoint</code> class represents a single method that 
    * is available from a service and can be invoked by the applciation.
    * 
    * @author Jared Kaplan
    */  
   public class ServiceEndpoint extends AbstractServiceEndpoint
   {
      private var m_baseUrl:String;
      private var m_url:String;
      private var m_method:String;
      private var m_contentType:String;
      private var m_nocache:Boolean;
      
      // service method constants
      static public const GET:String = "GET";
      static public const POST:String = "POST";
      
      // content type constants
      static public const DATA_CONTENT:String = "data";
      static public const MEDIA_CONTENT:String = "media";
      
      // global salt increment
      static private var s_saltCount:int = 0;

      public function ServiceEndpoint() {
      }
      
      /**
       * @inheritDoc 
       */  
      public override function createServiceTask( data:Object = null, completeHandler:Function = null, errorHandler:Function = null, progressHandler:Function = null ) : IServiceTask 
      {
         var serviceTask:IServiceTask = null;
         
         switch( contentType )
         {
            case MEDIA_CONTENT:
               serviceTask = new MediaServiceTask( createURLRequest( createParams( data ) ), this );
               break;
            
            default:
               serviceTask = new DataServiceTask( createURLRequest( createParams( data ) ), this );
         }
            
         // add a service notification handler if notification is requested
         if( completeHandler != null || errorHandler != null || progressHandler != null )
            serviceTask.addNotificationHandler( new ServiceNotificationHandler( completeHandler, errorHandler, progressHandler ) )
         
         return serviceTask;
      }
                  
      /**
       * Returns the service url, which is the concatination of the <code>url</code>
       * property with the <code>baseUrl</code> property.
       */  
      private function getServiceUrl() : String 
      {   	   
         var serviceUrl:String = null;
         
         // if a baseUrl has been specified then concatinate the url property
         // with it to create the service url
         if( baseUrl != null )
         {
            if( url != null )
               serviceUrl = (baseUrl.charAt( baseUrl.length - 1 ) == '/') ? baseUrl + url : baseUrl + "/" + url;
            else
               serviceUrl = baseUrl;
         }
         else
            serviceUrl = url;

         return serviceUrl;   	    
      }

      /**
       * Creates a URLRequest object that is used by the service task to invoke
       * the service method. The service parameters are either added to the request
       * as POST data if the service method is POST, or as url arguments if this
       * is a GET request.
       * 
       * @param data             Values that will be sent to the service with the request
       * @param headers          Optional headers that will be added to the request
       */  
      private function createURLRequest( data:Object = null, headers:Array = null ) : URLRequest
      {
         var request:URLRequest = null;
         
         if( method == POST )
         {
            request = new URLRequest( getServiceUrl() );
            request.data = createUrlVariables( data );
         }
         else
            request = new URLRequest( getUrl( data ) );
         
         // add the request headers
         if( headers != null )
         {
            for each( var next:URLRequestHeader in headers )
            {
               if( next != null )
                  request.requestHeaders.push( next );
            }
         }
         
         // set the request method
         request.method = method;
         
         return request;
      }
      
      /**
       * Returns the url string for this endpoint including all of the arguments
       * that are being specified.
       * 
       * @param data             Values that will be sent to the service with the request
       */ 
      private function getUrl( data:Object = null ) : String
      {
         var url:String = getServiceUrl();
         
         if( nocache || data != null )
         {
            var firstParam:Boolean = true
            
            url += "?";
            
            // add salt param
            if( nocache ) 
            {
               url = addParamToUrlString( url, "salt", getSalt(), firstParam );
               firstParam = false;
            }
            
            if( data != null )
            {
               var vars:URLVariables = createUrlVariables( data );

               for( var next:String in vars ) 
               {
                  url = addParamToUrlString( url, next, vars[next], firstParam );
                  firstParam = false; 
               }
            }
         }
         
         return url;
      }
      
      /**
       * Appends a single argument to the url string.
       * 
       * @param url              The url string to append the specified argument to
       * @param name             The name of the argument to be appended
       * @param value            The value of the argument to be appended
       * @param firstParam       Set to true if the specified argurment represents the
       *                         first argument being appended
       */ 
      private function addParamToUrlString( url:String, name:String, value:String, firstParam:Boolean ) : String
      {
         var baseUrl:String = url != null ? url : new String();
         
         if( firstParam )
            return baseUrl + name + "=" + value;
         else
            return baseUrl + "&" + name + "=" + value;
      }      

      /**
       * Returns the value used when the endpoint requires a salt argument 
       * to be added to the url.
       */ 
      private function getSalt() : String {
         return String( new Date().getTime() + (s_saltCount++) );
      }

      //
      // getter/setter methods
      //
      public function get baseUrl() : String { return m_baseUrl; }
      public function set baseUrl( baseUrl:String ) : void { m_baseUrl = baseUrl; }
      
      public function get url() : String { return m_url; }
      public function set url( url:String ) : void { m_url = url; }
      
      public function get method() : String { return m_method != null ? m_method : GET; }
      public function set method( method:String ) : void { m_method = method; }
      
      public function get contentType() : String { return m_contentType != null ? m_contentType : DATA_CONTENT; }
      public function set contentType( contentType:String ) : void { m_contentType = contentType; }
      
      public function get nocache() : Boolean { return m_nocache; }
      public function set nocache( nocache:Boolean ) : void { m_nocache = nocache; }
   }
}